import mxgraph from "src/pages/mxgraph/graph/index";

import canvasUtil from "src/components/Graph/classes/canvasUtil";

const {
  mxConstants,
  mxPoint,
  mxEventObject,
  mxEvent,
  mxUtils,
  mxClient,
  mxGraphView,
  mxRectangle,
  mxRectangleShape,
  mxMouseEvent
} = mxgraph;
/**
   * Function: getGraphBounds
   *
   * Overrides getGraphBounds to use bounding box from SVG.
   */
mxGraphView.prototype.getGraphBounds = function () {
  let b = this.graphBounds;
  if (this.graph.useCssTransforms) {
    let t = this.graph.currentTranslate;
    let s = this.graph.currentScale;
    b = new mxRectangle(
      (b.x + t.x) * s,
      (b.y + t.y) * s,
      b.width * s,
      b.height * s
    );
  }
  return b;
};

/**
   * Function: viewStateChanged
   *
   * Overrides to bypass full cell tree validation.
   * TODO: Check if this improves performance
   */
mxGraphView.prototype.viewStateChanged = function () {
  if (this.graph.useCssTransforms) {
    this.validate();
    this.graph.sizeDidChange();
  } else {
    this.revalidate();
    this.graph.sizeDidChange();
  }
};

/**
   * Function: validate
   *
   * Overrides validate to normalize validation view state and pass
   * current state to CSS transform.
   */
let graphViewValidate = mxGraphView.prototype.validate;

mxGraphView.prototype.validate = function (cell) {
  if (this.graph.useCssTransforms) {
    this.graph.currentScale = this.scale;
    this.graph.currentTranslate.x = this.translate.x;
    this.graph.currentTranslate.y = this.translate.y;

    this.scale = 1;
    this.translate.x = 0;
    this.translate.y = 0;
  }

  graphViewValidate.apply(this, arguments);

  if (this.graph.useCssTransforms) {
    this.graph.updateCssTransform();

    this.scale = this.graph.currentScale;
    this.translate.x = this.graph.currentTranslate.x;
    this.translate.y = this.graph.currentTranslate.y;
  }
};

	  /**
   * Reset the list of processed edges.
   */
let mxGraphViewResetValidationState =
    mxGraphView.prototype.resetValidationState;

mxGraphView.prototype.resetValidationState = function () {
  mxGraphViewResetValidationState.apply(this, arguments);
  this.validEdges = [];
};

/**
   * Updates jumps for valid edges and repaints if needed.
   */
let mxGraphViewValidateCellState = mxGraphView.prototype.validateCellState;

mxGraphView.prototype.validateCellState = function (cell, recurse) {
  recurse = recurse != null ? recurse : true;
  let state = this.getState(cell);

  //// Forces repaint if jumps change on a valid edge
  if (
    state != null &&
      recurse &&
      this.graph.model.isEdge(state.cell) &&
      state.style != null &&
      state.style[mxConstants.STYLE_CURVED] != 1 &&
      !state.invalid &&
      this.updateLineJumps(state)
  ) {
    this.graph.cellRenderer.redraw(state, false, this.isRendering());
  }

  state = mxGraphViewValidateCellState.apply(this, arguments);

  //// Adds to the list of edges that may intersect with later edges
  if (
    state != null &&
      recurse &&
      this.graph.model.isEdge(state.cell) &&
      state.style != null &&
      state.style[mxConstants.STYLE_CURVED] != 1
  ) {
    //// LATER: Reuse jumps for valid edges
    this.validEdges.push(state);
  }

  return state;
};

	  /**
   * Updates jumps for invalid edges.
   */
let mxGraphViewUpdateCellState = mxGraphView.prototype.updateCellState;

mxGraphView.prototype.updateCellState = function (state) {
  mxGraphViewUpdateCellState.apply(this, arguments);

  //// Updates jumps on invalid edge before repaint
  if (
    this.graph.model.isEdge(state.cell) &&
      state.style[mxConstants.STYLE_CURVED] != 1
  ) {
    this.updateLineJumps(state);
  }
};

/**
   * Updates the jumps between given state and processed edges.
   */
mxGraphView.prototype.updateLineJumps = function (state) {
  let pts = state.absolutePoints;

  if (this.graph.lineJumpsEnabled) {
    let changed = state.routedPoints != null;
    let actual = null;

    if (
      pts != null &&
        this.validEdges != null &&
        mxUtils.getValue(state.style, "jumpStyle", "none") !== "none"
    ) {
      let thresh = 0.5 * this.scale;
      changed = false;
      actual = [];

      //// Type 0 means normal waypoint, 1 means jump
      //// eslint-disable-next-line no-inner-declarations
      function addPoint(type, x, y) {
        let rpt = new mxPoint(x, y);
        rpt.type = type;

        actual.push(rpt);
        let curr =
            state.routedPoints != null
              ? state.routedPoints[actual.length - 1]
              : null;

        return (
          curr == null || curr.type != type || curr.x != x || curr.y != y
        );
      }

      for (let i = 0; i < pts.length - 1; i++) {
        let p1 = pts[i + 1];
        let p0 = pts[i];
        let list = [];

        //// Ignores waypoints on straight segments
        let pn = pts[i + 2];

        while (
          i < pts.length - 2 &&
            mxUtils.ptSegDistSq(p0.x, p0.y, pn.x, pn.y, p1.x, p1.y) <
            1 * this.scale * this.scale
        ) {
          p1 = pn;
          i++;
          pn = pts[i + 2];
        }

        changed = addPoint(0, p0.x, p0.y) || changed;

        //// Processes all previous edges
        for (let e = 0; e < this.validEdges.length; e++) {
          let state2 = this.validEdges[e];
          let pts2 = state2.absolutePoints;

          if (
            pts2 != null &&
              mxUtils.intersects(state, state2) &&
              state2.style["noJump"] != "1"
          ) {
            //// Compares each segment of the edge with the current segment
            for (let j = 0; j < pts2.length - 1; j++) {
              let p3 = pts2[j + 1];
              let p2 = pts2[j];

              //// Ignores waypoints on straight segments
              pn = pts2[j + 2];

              while (
                j < pts2.length - 2 &&
                  mxUtils.ptSegDistSq(p2.x, p2.y, pn.x, pn.y, p3.x, p3.y) <
                  1 * this.scale * this.scale
              ) {
                p3 = pn;
                j++;
                pn = pts2[j + 2];
              }

              let pt = mxUtils.intersection(
                p0.x,
                p0.y,
                p1.x,
                p1.y,
                p2.x,
                p2.y,
                p3.x,
                p3.y
              );

              //// Handles intersection between two segments
              if (
                pt != null &&
                  (Math.abs(pt.x - p0.x) > thresh ||
                    Math.abs(pt.y - p0.y) > thresh) &&
                  (Math.abs(pt.x - p1.x) > thresh ||
                    Math.abs(pt.y - p1.y) > thresh) &&
                  (Math.abs(pt.x - p2.x) > thresh ||
                    Math.abs(pt.y - p2.y) > thresh) &&
                  (Math.abs(pt.x - p3.x) > thresh ||
                    Math.abs(pt.y - p3.y) > thresh)
              ) {
                let dx = pt.x - p0.x;
                let dy = pt.y - p0.y;
                let temp = {
                  distSq: dx * dx + dy * dy,
                  x: pt.x,
                  y: pt.y
                };

                //// Intersections must be ordered by distance from start of segment
                for (let t = 0; t < list.length; t++) {
                  if (list[t].distSq > temp.distSq) {
                    list.splice(t, 0, temp);
                    temp = null;

                    break;
                  }
                }

                //// Ignores multiple intersections at segment joint
                if (
                  temp != null &&
                    (list.length == 0 ||
                      list[list.length - 1].x !== temp.x ||
                      list[list.length - 1].y !== temp.y)
                ) {
                  list.push(temp);
                }
              }
            }
          }
        }

        //// Adds ordered intersections to routed points
        for (let j = 0; j < list.length; j++) {
          changed = addPoint(1, list[j].x, list[j].y) || changed;
        }
      }

      let pt = pts[pts.length - 1];
      changed = addPoint(0, pt.x, pt.y) || changed;
    }

    state.routedPoints = actual;

    return changed;
  } else {
    return false;
  }
};
/**
   * Adds support for snapToPoint style.
   */
let mxGraphViewUpdateFloatingTerminalPoint =
    mxGraphView.prototype.updateFloatingTerminalPoint;

mxGraphView.prototype.updateFloatingTerminalPoint = function (
  edge,
  start,
  end,
  source
) {
  if (
    start != null &&
      edge != null &&
      (start.style["snapToPoint"] == "1" || edge.style["snapToPoint"] == "1")
  ) {
    start = this.getTerminalPort(edge, start, source);
    let next = this.getNextPoint(edge, end, source);

    let orth = this.graph.isOrthogonal(edge);
    let alpha = mxUtils.toRadians(
      Number(start.style[mxConstants.STYLE_ROTATION] || "0")
    );
    let center = new mxPoint(start.getCenterX(), start.getCenterY());

    if (alpha != 0) {
      let cos = Math.cos(-alpha);
      let sin = Math.sin(-alpha);
      next = mxUtils.getRotatedPoint(next, cos, sin, center);
    }
    //// TODO
    let border = parseFloat(
      edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0
    );
    //// TODO
    border += parseFloat(
      edge.style[
        source
          ? mxConstants.STYLE_SOURCE_PERIMETER_SPACING
          : mxConstants.STYLE_TARGET_PERIMETER_SPACING
      ] || 0
    );
    let pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);

    if (alpha != 0) {
      let cos = Math.cos(alpha);
      let sin = Math.sin(alpha);
      pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
    }

    edge.setAbsoluteTerminalPoint(
      this.snapToAnchorPoint(edge, start, end, source, pt),
      source
    );
  } else {
    mxGraphViewUpdateFloatingTerminalPoint.apply(this, arguments);
  }
};

mxGraphView.prototype.snapToAnchorPoint = function (
  edge,
  start,
  end,
  source,
  pt
) {
  if (start != null && edge != null) {
    let constraints = this.graph.getAllConnectionConstraints(start);
    let nearest = null;
    let dist = null;

    if (constraints != null) {
      for (let i = 0; i < constraints.length; i++) {
        let cp = this.graph.getConnectionPoint(start, constraints[i]);

        if (cp != null) {
          let tmp =
              (cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y);

          if (dist == null || tmp < dist) {
            nearest = cp;
            dist = tmp;
          }
        }
      }
    }

    if (nearest != null) {
      pt = nearest;
    }
  }

  return pt;
};

//// Defines grid properties
mxGraphView.prototype.gridImage = mxClient.IS_SVG
  ? ""
  : IMAGE_PATH + "/grid.gif";
mxGraphView.prototype.gridSteps = 4;
mxGraphView.prototype.minGridSize = 4;

//// UrlParams is null in embed mode
mxGraphView.prototype.gridColor = "#e0e0e0";

//// Units
mxGraphView.prototype.unit = mxConstants.POINTS;

mxGraphView.prototype.setUnit = function (unit) {
  if (this.unit != unit) {
    this.unit = unit;

    this.fireEvent(new mxEventObject("unitChanged", "unit", unit));
  }
};
//// let graphViewValidateBackgroundPage =
//// mxGraphView.prototype.validateBackgroundPage;

//// mxGraphView.prototype.validateBackgroundPage = function () {
////   let useCssTranforms = this.graph.useCssTransforms,
////     scale = this.scale,
////     translate = this.translate;

////   if (useCssTranforms) {
////     this.scale = this.graph.currentScale;
////     this.translate = this.graph.currentTranslate;
////   }

////   graphViewValidateBackgroundPage.apply(this, arguments);

////   if (useCssTranforms) {
////     this.scale = scale;
////     this.translate = translate;
////   }
//// };
//// mxGraphView.prototype.formatUnitText = function (pixels) {
////   return pixels ? canvasUtil.formatHintText(pixels, this.unit) : pixels;
//// };

//// Uses HTML for background pages (to support grid background image)
mxGraphView.prototype.validateBackgroundPage = function () {
  let graph = this.graph;

  if (graph.container != null && !graph.transparentBackground) {
    if (graph.pageVisible) {
      let bounds = this.getBackgroundPageBounds();

      if (this.backgroundPageShape == null) {
        //// Finds first element in graph container
        let firstChild = graph.container.firstChild;

        while (
          firstChild != null &&
            firstChild.nodeType !== mxConstants.NODETYPE_ELEMENT
        ) {
          firstChild = firstChild.nextSibling;
        }

        if (firstChild != null) {
          this.backgroundPageShape = this.createBackgroundPageShape(bounds);

          this.backgroundPageShape.scale = 1;

          //// Shadow filter causes problems in outline window in quirks mode. IE8 standards
          //// also has known rendering issues inside mxWindow but not using shadow is worse.
          this.backgroundPageShape.isShadow = !mxClient.IS_QUIRKS;
          this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
          this.backgroundPageShape.init(graph.container);

          //// Required for the browser to render the background page in correct order
          firstChild.style.position = 'absolute';
          graph.container.insertBefore(
            this.backgroundPageShape.node,
            firstChild
          );
          this.backgroundPageShape.redraw();

          this.backgroundPageShape.node.className = 'geBackgroundPage';

          //// Adds listener for double click handling on background
          mxEvent.addListener(
            this.backgroundPageShape.node,
            'dblclick',
            mxUtils.bind(this, function (evt) {
              graph.dblClick(evt);
            })
          );

          //// adds basic listeners for graph event dispatching outside of the
          //// container and finishing the handling of a single gesture
          mxEvent.addGestureListeners(
            this.backgroundPageShape.node,
            mxUtils.bind(this, function (evt) {
              graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
            }),
            mxUtils.bind(this, function (evt) {
              //// Hides the tooltip if mouse is outside container
              if (
                graph.tooltipHandler != null &&
                  graph.tooltipHandler.isHideOnHover()
              ) {
                graph.tooltipHandler.hide();
              }

              if (graph.isMouseDown && !mxEvent.isConsumed(evt)) {
                graph.fireMouseEvent(
                  mxEvent.MOUSE_MOVE,
                  new mxMouseEvent(evt)
                );
              }
            }),
            mxUtils.bind(this, function (evt) {
              graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
            })
          );
        }
      } else {
        this.backgroundPageShape.scale = 1;
        this.backgroundPageShape.bounds = bounds;
        this.backgroundPageShape.redraw();
      }
    } else if (this.backgroundPageShape != null) {
      this.backgroundPageShape.destroy();
      this.backgroundPageShape = null;
    }

    this.validateBackgroundStyles();
  }
};

//// Updates the CSS of the background to draw the grid
mxGraphView.prototype.validateBackgroundStyles = function () {
  let graph = this.graph;
  let color =
      graph.background == null || graph.background == mxConstants.NONE
        ? graph.defaultPageBackgroundColor
        : graph.background;
  let gridColor =
      color != null && this.gridColor != color.toLowerCase()
        ? this.gridColor
        : '#ffffff';
  let image = 'none';
  let position = '';

  if (graph.isGridEnabled()) {
    let phase = 10;

    if (mxClient.IS_SVG) {
      //// Generates the SVG required for drawing the dynamic grid
      image = unescape(encodeURIComponent(this.createSvgGrid(gridColor)));
      image = (window.btoa) ? btoa(image) : window.Base64.encode(image, true);
      image = 'url(' + 'data:image/svg+xml;base64,' + image + ')';
      phase = graph.gridSize * this.scale * this.gridSteps;
    } else {
      //// Fallback to grid wallpaper with fixed size
      image = 'url(' + this.gridImage + ')';
    }

    let x0 = 0;
    let y0 = 0;

    if (graph.view.backgroundPageShape != null) {
      let bds = this.getBackgroundPageBounds();
      x0 = 1 + bds.x;
      y0 = 1 + bds.y;
    }

    //// Computes the offset to maintain origin for grid
    position =
        -Math.round(
          phase - mxUtils.mod(this.translate.x * this.scale - x0, phase)
        ) +
        'px ' +
        -Math.round(
          phase - mxUtils.mod(this.translate.y * this.scale - y0, phase)
        ) +
        'px';
  }

  let canvas = graph.view.canvas;

  if (canvas.ownerSVGElement != null) {
    canvas = canvas.ownerSVGElement;
  }

  if (graph.view.backgroundPageShape != null) {
    //// 设置的是svg画布的geBackgroundPage样式（不是svg的）
    graph.view.backgroundPageShape.node.style.backgroundPosition = position;
    graph.view.backgroundPageShape.node.style.backgroundImage = image;
    graph.view.backgroundPageShape.node.style.backgroundColor = color;
    graph.view.backgroundPageShape.node.style.left = '5px';
    graph.container.className = 'geDiagramContainer geDiagramBackdrop';
    graph.container.style.top = '0px';
    graph.container.style.background = 'linear-gradient(rgb(251, 253, 255) 0%, rgb(230, 246, 255) 100%, rgb(202, 228, 243) 100%, rgb(171, 215, 239) 100%)';
    canvas.style.backgroundImage = 'none';
    canvas.style.backgroundColor = '';
    canvas.style.left = '5px';

  } else {
    graph.container.className = 'geDiagramContainer';
    canvas.style.backgroundPosition = position;
    canvas.style.backgroundColor = color;
    canvas.style.backgroundImage = image;
  }
};

//// Returns the SVG required for painting the background grid.
mxGraphView.prototype.createSvgGrid = function (color) {
  let tmp = this.graph.gridSize * this.scale;

  while (tmp < this.minGridSize) {
    tmp *= 2;
  }

  let tmp2 = this.gridSteps * tmp;

  //// Small grid lines
  let d = [];

  for (let i = 1; i < this.gridSteps; i++) {
    let tmp3 = i * tmp;
    d.push(
      'M 0 ' +
        tmp3 +
        ' L ' +
        tmp2 +
        ' ' +
        tmp3 +
        ' M ' +
        tmp3 +
        ' 0 L ' +
        tmp3 +
        ' ' +
        tmp2
    );
  }

  //// KNOWN: Rounding errors for certain scales (eg. 144%, 121% in Chrome, FF and Safari). Workaround
  //// in Chrome is to use 100% for the svg size, but this results in blurred grid for large diagrams.
  let size = tmp2;
  let svg =
      '<svg width="' +
      size +
      '" height="' +
      size +
      '" xmlns="' +
      mxConstants.NS_SVG +
      '">' +
      '<defs><pattern id="grid" width="' +
      tmp2 +
      '" height="' +
      tmp2 +
      '" patternUnits="userSpaceOnUse">' +
      '<path d="' +
      d.join(' ') +
      '" fill="none" stroke="' +
      color +
      '" opacity="0.2" stroke-width="1"/>' +
      '<path d="M ' +
      tmp2 +
      ' 0 L 0 0 0 ' +
      tmp2 +
      '" fill="none" stroke="' +
      color +
      '" stroke-width="1"/>' +
      '</pattern></defs><rect width="100%" height="100%" fill="url(#grid)"/></svg>';

  return svg;
};
//// Creates background page shape
mxGraphView.prototype.createBackgroundPageShape = function (bounds) {
  return new mxRectangleShape(
    bounds,
    '#ffffff',
    this.graph.defaultPageBorderColor
  );
};

//// Fits the number of background pages to the graph
mxGraphView.prototype.getBackgroundPageBounds = function () {
  let gb = this.getGraphBounds();

  //// Computes unscaled, untranslated graph bounds
  let x = gb.width > 0 ? gb.x / this.scale - this.translate.x : 0;
  let y = gb.height > 0 ? gb.y / this.scale - this.translate.y : 0;
  let w = gb.width / this.scale;
  let h = gb.height / this.scale;

  let fmt = this.graph.pageFormat;
  let ps = this.graph.pageScale;

  let pw = fmt.width * ps;
  let ph = fmt.height * ps;

  let x0 = Math.floor(Math.min(0, x) / pw);
  let y0 = Math.floor(Math.min(0, y) / ph);
  let xe = Math.ceil(Math.max(1, x + w) / pw);
  let ye = Math.ceil(Math.max(1, y + h) / ph);

  let rows = xe - x0;
  let cols = ye - y0;

  let bounds = new mxRectangle(
    this.scale * (this.translate.x + x0 * pw),
    this.scale * (this.translate.y + y0 * ph),
    this.scale * rows * pw,
    this.scale * cols * ph
  );

  return bounds;
};
